package com.nihao001.parse;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.jsoup.helper.DataUtil;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.google.gson.Gson;
import com.nihao001.parse.config.Configuration;
import com.nihao001.parse.config.HeaderConfig;
import com.nihao001.parse.config.Parameter;
import com.nihao001.parse.config.ParameterConfig;
import com.nihao001.parse.config.ParseConfig;
import com.nihao001.parse.config.Result;
import com.nihao001.parse.config.ResultConfig;
import com.nihao001.parse.exception.NotFoundException;
import com.nihao001.parse.exception.ParseException;
import com.nihao001.parse.exception.TooManyResultException;
import com.nihao001.parse.handler.Handler;
import com.nihao001.parse.reader.DefaultReader;
import com.nihao001.parse.reader.Reader;

/**
 * 
 * <p>
 * Description:解析文本客户端
 * </p>
 * 
 * <p>
 * Author: wang xing yue
 * </p>
 * <p>
 * Date :Nov 12, 2013
 * </p>
 * 
 */

public class ParseClient {

	// 文本解析器
	private static final String TEXT_HANDLER = "com.nihao001.parse.handler.impl.TextHandler";

	// 元素属性解析器
	private static final String ATTRIBUTE_HANDLER = "com.nihao001.parse.handler.impl.AttributeHandler";

	// html解析解析器
	private static final String HTML_HANDLER = "com.nihao001.parse.handler.impl.FullHtmlHandler";

	private Configuration configuration = null;

	private Reader reader = null;

	private ParseClient(Configuration configuration, Reader reader) {
		this.configuration = configuration;
		this.reader = reader;
	}

	public ParseClient(Configuration configuration) {
		this.configuration = configuration;
		this.reader = new DefaultReader();
	}

	public static ParseClient createInstant(Configuration configuration) {
		return new ParseClient(configuration, new DefaultReader());
	}

	public static ParseClient createInstant(Configuration configuration,
			Reader reader) {
		return new ParseClient(configuration, reader);
	}

	/**
	 * 解析文本
	 * 
	 * @param id
	 *            配置文件里parse元素的id
	 * @param map
	 *            参数
	 * @return
	 * @throws ParseException
	 */
	private List parse(String id, Object parameter,
			Map<String, String> cookies, Map<String, String> headers)
			throws ParseException {
		ParseConfig parseConfig = configuration.getParseConfig(id);
		if (parseConfig == null) {
			throw new IllegalArgumentException(
					"this is no configuration named \"" + id
							+ "\" is the config file.");
		}

		String url = parseConfig.getUrl();
		Method method = parseConfig.getMethod();
		String responseEncode = parseConfig.getResponseEncode();
		String requestEncode = parseConfig.getRequestEncode();
		String proxyHost = parseConfig.getProxyHost();
		int proxyPort = parseConfig.getProxyPort();
		ParameterConfig parameterConfig = parseConfig.getParameterConfig();
		ResultConfig resultConfig = parseConfig.getResultConfig();

		Map<String, String> data = generateParameterMap(cookies, parseConfig,
				parameter, parameterConfig);

		Response response = null;

		// 给reader设置代理
		this.reader.setProxy(proxyHost, proxyPort);

		if (StringUtils.isEmpty(requestEncode)) {
			requestEncode = DataUtil.defaultCharset;
		}
		if (method == Method.GET) {
			response = this.reader.get(url, requestEncode, data, cookies,
					headers);
		} else {
			response = this.reader.post(url, requestEncode, data, cookies,
					headers);
		}
		if (response == null) {
			return null;
		}

		String text = null;
		if (responseEncode != null && !responseEncode.trim().equals("")) {
			text = response.body(responseEncode);
		} else {
			text = response.body();
		}
		List list = null;
		if (text != null) {
			try {
				Document doc = Jsoup.parse(text);
				list = parseDocument(text, doc, resultConfig);
				if (list != null && list.size() > 0) {
					Object obj = list.get(0);
					if (obj instanceof HttpResult) {
						for (Object oo : list) {
							HttpResult httpResult = (HttpResult) oo;
							httpResult.setResponse(response);
						}
					}
				}
			} catch (Exception e) {
				throw new ParseException(new ParseTextException(e));
			}
		}
		return list;
	}

	/**
	 * 把输入的参数转换为Map<String, String>
	 * 
	 * @param inputParameterObject
	 * @param parameterConfig
	 * @return
	 * @throws ParseException
	 */
	private Map<String, String> generateParameterMap(
			Map<String, String> cookies, ParseConfig parseConfig,
			Object inputParameterObject, ParameterConfig parameterConfig)
			throws ParseException {
		Map<String, String> map = new HashMap<String, String>();
		if (inputParameterObject == null || parameterConfig == null) {
			return map;
		}
		Map<String, Parameter> paramDefinationMap = parameterConfig
				.getParameterMap();
		String clazz = parameterConfig.getClassFullName();

		// inputParameterObject is Map
		if (clazz.equals(DefaultConstantMap.MAP_FULL_NAME)) {
			Map<String, String> inputParamMap = (Map<String, String>) inputParameterObject;
			Set<Map.Entry<String, Parameter>> set = paramDefinationMap
					.entrySet();
			for (Map.Entry<String, Parameter> entry : set) {
				Parameter parameter = entry.getValue();
				map.put(parameter.getParameterName(),
						inputParamMap.get(parameter.getPropertyName()));

			}
		}
		// obj is Object(JavaBean)
		else {
			Set<Map.Entry<String, Parameter>> set = paramDefinationMap
					.entrySet();
			for (Map.Entry<String, Parameter> entry : set) {
				Parameter parameter = entry.getValue();
				String result = "";
				Field field;
				try {
					field = inputParameterObject.getClass()
							.getDeclaredField(parameter.getPropertyName());
					field.setAccessible(true);
					result = "" + field.get(inputParameterObject);
				} catch (Exception e) {
					throw new ParseException(e);
				}

				map.put(parameter.getParameterName(), result);
			}
		}
		return map;
	}

	/**
	 * 解析文本
	 * 
	 * @param doc
	 * @param resultConfig
	 * @return
	 * @throws ParseException
	 */
	private List parseDocument(String html, Document doc,
			ResultConfig resultConfig) throws ParseException {
		String childClassName = resultConfig.getChildClass();
		String parentSelector = resultConfig.getSelector();
		String clazz = resultConfig.getClassFullName();
		Map<String, Result> resultDefinationMap = resultConfig.getResultMap();

		Elements eles = null;

		try {
			eles = doc.select(parentSelector);
		} catch (Exception e) {
			throw new ParseException(e);
		}
		if (clazz.equals(DefaultConstantMap.LIST_FULL_NAME)) {
			return parseChildElements(html, doc, eles, childClassName,
					resultDefinationMap);
		} else {
			return parseChildElements(html, doc, eles, clazz,
					resultDefinationMap);
		}

	}

	/**
	 * 解析子元素
	 * 
	 * @param doc
	 * @param eles
	 * @param childClassName
	 * @param resultDefinationMap
	 * @return
	 * @throws ParseException
	 */
	private List parseChildElements(String html, Document doc, Elements eles,
			String childClassName, Map<String, Result> resultDefinationMap)
			throws ParseException {
		List list = new ArrayList();
		if (eles == null || eles.size() == 0) {
			return list;
		}

		for (Element e : eles) {
			Class clazz1 = null;
			Object childObj = null;
			try {
				clazz1 = Class.forName(childClassName);
				childObj = clazz1.newInstance();
			} catch (Exception e1) {
				throw new ParseException(e1);
			}

			Map mapObj = null;
			if (childClassName.equals(DefaultConstantMap.MAP_FULL_NAME)) {
				mapObj = (HashMap) childObj;
			} else {
				// do nothing, because "childObj" is Javabean
			}

			Set<Map.Entry<String, Result>> set = resultDefinationMap.entrySet();
			for (Map.Entry<String, Result> entry : set) {
				Result result = entry.getValue();
				String[] resultSelectorInfo = result.getSelector().split("\\|");
				String resultHandlerClass = result.getHandler();
				String resultClass = result.getClazz();
				String resultPropertyName = result.getPropertyName();

				// 选择一个handler
				Handler hander = chooseHandler(resultSelectorInfo,
						resultHandlerClass);
				String valObject = null;

				// 是绝对路径的话，是解析整个document
				if (result.isAbsolute()) {
					valObject = hander.parse(html,
							doc.select(resultSelectorInfo[0]));
				} else {
					valObject = hander.parse(html,
							e.select(resultSelectorInfo[0]));
				}

				// string
				if (resultClass.equals(DefaultConstantMap.STRING_FULL_NAME)) {
					this.setValue(mapObj, childObj, String.class,
							resultPropertyName, valObject);
				}
				// int
				else if (resultClass.equals(DefaultConstantMap.INT_FULL_NAME)) {
					this.setValue(mapObj, childObj, Integer.class,
							resultPropertyName, valObject);
				}
				// long
				else if (resultClass.equals(DefaultConstantMap.LONG_FULL_NAME)) {
					this.setValue(mapObj, childObj, Long.class,
							resultPropertyName, valObject);
				}
				// short
				else if (resultClass.equals(DefaultConstantMap.SHORT_FULL_NAME)) {
					this.setValue(mapObj, childObj, Short.class,
							resultPropertyName, valObject);
				}
				// float
				else if (resultClass.equals(DefaultConstantMap.FLOAT_FULL_NAME)) {
					this.setValue(mapObj, childObj, Float.class,
							resultPropertyName, valObject);
				}
				// double
				else if (resultClass
						.equals(DefaultConstantMap.DOUBLE_FULL_NAME)) {
					this.setValue(mapObj, childObj, Double.class,
							resultPropertyName, valObject);

				} else if (resultClass
						.equals(DefaultConstantMap.BOOL_FULL_NAME)) {
					this.setValue(mapObj, childObj, Boolean.class,
							resultPropertyName, valObject);
				} else {
					throw new ParseException("can not support class:"
							+ resultClass);
				}
			}

			if (mapObj != null) {
				list.add(mapObj);
			} else {
				list.add(childObj);
			}
		}
		return list;

	}

	/**
	 * 给对象、Map设置解析后的值
	 * 
	 * @param mapObj
	 *            map
	 * @param childObj
	 *            对象
	 * @param clazz
	 *            解析出来的值要转化成的类型
	 * @param propertyName
	 *            目标对象的属性或者map的key
	 * @param valueObj
	 *            解析出来的值
	 * @throws ParseException
	 */
	private void setValue(Map mapObj, Object childObj, Class clazz,
			String propertyName, String valueObj) throws ParseException {
		if (valueObj == null || ((String) valueObj).trim().equals("")) {
			return;
		}
		Object value;
		try {
			value = clazz.getConstructor(String.class).newInstance(
					(String) valueObj);
			if (mapObj != null) {
				mapObj.put(propertyName, value);
			} else {
				Field filed = childObj.getClass()
						.getDeclaredField(propertyName);
				filed.setAccessible(true);
				filed.set(childObj, value);
			}
		} catch (Exception e) {
			throw new ParseException(e);
		}

	}

	/**
	 * 选择一个handler，用于解析
	 * 
	 * @param resultSelectorInfo
	 * @param resultHandlerClass
	 * @return
	 * @throws ParseException
	 */
	private Handler chooseHandler(String[] resultSelectorInfo,
			String resultHandlerClass) throws ParseException {
		WhereToParse whereToParse = null;

		// html
		if (resultSelectorInfo.length == 1
				&& resultSelectorInfo[0].equalsIgnoreCase("html")) {
			resultHandlerClass = HTML_HANDLER;
		}
		// a img|{type:attr, value:src}
		else if (resultSelectorInfo.length == 2) {
			String selectPart2 = resultSelectorInfo[1];
			Gson gson = new Gson();
			whereToParse = gson.fromJson(selectPart2, WhereToParse.class);
			if (whereToParse.getType().toLowerCase().equals("text")) {
				resultHandlerClass = TEXT_HANDLER;
			} else if (whereToParse.getType().toLowerCase().equals("attr")) {
				resultHandlerClass = ATTRIBUTE_HANDLER;
			} else {
				throw new ParseException(
						"can not find class implement of Handler. The type you defined is:"
								+ whereToParse.getType());
			}
		}
		Handler hander = null;
		try {
			Class handlerClass = Class.forName(resultHandlerClass);
			if (resultHandlerClass.equals(ATTRIBUTE_HANDLER)) {
				hander = (Handler) handlerClass.getConstructors()[0]
						.newInstance(whereToParse.getValue());
			} else {
				hander = (Handler) handlerClass.newInstance();
			}
		} catch (Exception e1) {
			throw new ParseException(e1);
		}
		return hander;
	}

	public Object parseForObject(String id, Object parameter,
			Map<String, String> cookies, Map<String, String> headers)
			throws ParseException {
		List list = this.parse(id, parameter, cookies, headers);
		if (list != null && list.size() == 1) {
			return list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Object parseForObject(String id, Map<String, String> cookies,
			Map<String, String> headers) throws ParseException {
		List list = this.parse(id, null, cookies, headers);
		if (list != null && list.size() == 1) {
			return list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Object parseForObject(String id, Object parameterObj)
			throws ParseException {
		List list = this.parse(id, parameterObj, null, null);
		if (list != null && list.size() == 1) {
			return list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Object parseForObject(String id) throws ParseException {
		List list = this.parse(id, null, null, null);
		if (list != null && list.size() == 1) {
			return list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Map parseForMap(String id, Object parameter,
			Map<String, String> cookies, Map<String, String> headers)
			throws ParseException {
		List list = this.parse(id, parameter, cookies, headers);
		if (list != null && list.size() == 1) {
			return (Map) list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Map parseForMap(String id, Map<String, String> cookies,
			Map<String, String> headers) throws ParseException {
		List list = this.parse(id, null, cookies, headers);
		if (list != null && list.size() == 1) {
			return (Map) list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Map parseForMap(String id, Object parameterObj)
			throws ParseException {
		List list = this.parse(id, parameterObj, null, null);
		if (list != null && list.size() == 1) {
			return (Map) list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public Map parseForMap(String id) throws ParseException {
		List list = this.parse(id, null, null, null);
		if (list != null && list.size() == 1) {
			return (Map) list.get(0);
		} else if (list == null || list.size() == 0) {
			throw new ParseException(new NotFoundException("no result found."));
		} else {
			throw new ParseException(new TooManyResultException(
					"more than 1 result found."));
		}
	}

	public List parseForList(String id, Object parameter,
			Map<String, String> cookies, Map<String, String> headers)
			throws ParseException {
		return this.parse(id, parameter, cookies, headers);
	}

	public List parseForList(String id, Map<String, String> cookies,
			Map<String, String> headers) throws ParseException {
		return this.parse(id, null, cookies, headers);
	}

	public List parseForList(String id, Object parameterObj)
			throws ParseException {
		return this.parse(id, parameterObj, null, null);
	}

	public List parseForList(String id) throws ParseException {
		return this.parse(id, null, null, null);
	}

	public Map<String, String> getHeaders(String id) {
		if (StringUtils.isEmpty(id)) {
			return null;
		}
		HeaderConfig headerConfig = this.configuration.getHeaderConfig(id);
		if (headerConfig == null) {
			return null;
		}
		return headerConfig.getHeaders();
	}

}
